﻿namespace Diplom.Graphics.D3D
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using Diplom.NumericArrays;
    using SystemDraw = System.Drawing;

    public class ColorVisualizerNonStatic
    {
        double Na;
        double Nb;
        int NomMin;
        int NomMax;
        double Grad;
        double KoefA, KoefB, KoefC;
        double dZ, GmaxZ, GminZ;
        int KIZO=10;
        double MinZ;
        double MaxZ;
        double EPS = 1e-9;
        List<Point3D> TRG;
        List<Point3D> S1;
        List<Point3D> MasPd1 = new List<Point3D>();
        List<Point3D> MasPd2 = new List<Point3D>();
        List<Point3D> MasP = new List<Point3D>();
        List<Point3D> Hull1 = new List<Point3D>();
        List<Point3D> Hull2 = new List<Point3D>();
        List<Point3D> Clip = new List<Point3D>();


        public ColorVisualizerNonStatic(int CountIzo)
        {
            KIZO = CountIzo;
        }

        double xj(int j)
        {
            return (j - 0.0) / (Nb - 1.0);
        }
        double yi(int i)
        {
            return 1.0 - (i - 0.0) / (Na - 1.0);
        }


        protected enum ClassPoint : int { BEHIND, BETWEEN, BEYOND, ORIGIN, DESTINATION, LEFT, RIGHT };
        protected enum ClassEdge : int { PARALLEL, COLLINEAR, SKEW, SKEW_CROSS, SKEW_NO_CROSS };

        double rk;
        

        public struct Point3D
        {
            public double x;
            public double y;
            public double z;

            public Point3D(double _x, double _y, double _z)
            {
                x = _x;
                y = _y;
                z = _z;
            }
        };
        public class tEdge
        {
            public Point3D org;
            public Point3D dest;

            public tEdge()
            {
                org = new Point3D();
                dest = new Point3D();
            }
            public tEdge(Point3D _org, Point3D _dest)
                : base()
            {
                org = _org;
                dest = _dest;
            }
        }

        double dotProduct(Point3D p1, Point3D p2)
        {
            return p1.x * p2.x + p1.y * p2.y;
        }
        
        void rPoint(double x, double y, ref Point3D p)
        {
            p.x = x;
            p.y = y;
        }
        
        void Swap(ref Point3D p1, ref Point3D p2)
        {
            Point3D tmp = p1;
            p1 = p2;
            p2 = tmp;
        }

        int Cmp(Point3D p1, Point3D p2)
        {
            if (p1.x + EPS < p2.x) { return -1; }
            else if (p1.x > EPS + p2.x) { return 1; }
            else if (p1.y + EPS < p2.y) { return -1; }
            else if (p1.y > EPS + p2.y) { return 1; }
            else return 0;
        }

        ClassPoint Classify(Point3D p, Point3D p0, Point3D p1)
        {
            Point3D a = new Point3D();
            Point3D b = new Point3D();
            double r1, len1, len2;
            a.x = p1.x - p0.x;
            a.y = p1.y - p0.y;
            b.x = p.x - p0.x;
            b.y = p.y - p0.y;
            r1 = a.x * b.y - a.y * b.x;
            if (r1 > EPS) return ClassPoint.LEFT;
            if (r1 < -EPS) return ClassPoint.RIGHT;
            if (a.x * b.x < -EPS || a.y * b.y < -EPS) return ClassPoint.BEHIND;
            len1 = a.x * a.x + a.y * a.y;
            len2 = b.x * b.x + b.y * b.y;
            if (len1 < len2) return ClassPoint.BEYOND;
            if (Math.Abs(p1.x - p.x)<EPS && Math.Abs(p1.y - p.y)<EPS) return ClassPoint.DESTINATION;
            if (Math.Abs(p0.x - p.x)<EPS && Math.Abs(p0.y - p.y)<EPS) return ClassPoint.ORIGIN;
            return ClassPoint.BETWEEN;
        }
        
        ClassEdge Intersect(tEdge aEdge, tEdge bEdge, ref double t)
        {
            Point3D a = aEdge.org;
            Point3D b = aEdge.dest;
            Point3D c = bEdge.org;
            Point3D d = bEdge.dest;

            Point3D n = new Point3D();
            Point3D rP = new Point3D();
            rPoint(d.y - c.y, c.x - d.x, ref n);
            rPoint(b.x - a.x, b.y - a.y, ref rP);

            t = .0;
            double denom = dotProduct(n, rP);
            ClassPoint aClass;
            if (Math.Abs(denom) <EPS)
            {
                aClass = Classify(aEdge.org, bEdge.org, bEdge.dest);
                if (aClass == ClassPoint.LEFT || aClass == ClassPoint.RIGHT)
                    return ClassEdge.PARALLEL;
                else
                    return ClassEdge.COLLINEAR;
            }
            else
            {
                rP.x = a.x - c.x;
                rP.y = a.y - c.y;
                double num = n.x * rP.x + n.y * rP.y;
                t = -num / denom;
                return ClassEdge.SKEW;
            }
        }
      
        ClassEdge Cross(tEdge aEdge, tEdge bEdge, ref double t)
        {
            double s = .0;
            ClassEdge crossType = Intersect(bEdge, aEdge, ref s);

            if (crossType == ClassEdge.COLLINEAR || crossType == ClassEdge.PARALLEL)
            {
                return crossType;
            }
            if (s < 0 || s > 1)
            {
                return ClassEdge.SKEW_NO_CROSS;
            }
            crossType = Intersect(aEdge, bEdge, ref t);
            if (t >= 0 && t <= 1)
            {
                return ClassEdge.SKEW_CROSS;
            }
            else
            {
                return ClassEdge.SKEW_NO_CROSS;
            }
        }

        void HullEdge(List<Point3D> S, ref tEdge e)
        {
            int m;
            ClassPoint c;

            m = 0;
            for (int i = 1; i < S.Count; i++)
            {
                if (Cmp(S[i], S[m]) < 0) m = i;
            }
            Point3D tmp = S[0];
            S[0] = S[m];
            S[m] = tmp;
            //Swap(S[0], S[m]);
            m = 1;
            for (int i = 2; i < S.Count; i++)
            {
                c = Classify(S[i], S[0], S[m]);
                if (c == ClassPoint.BETWEEN || c == ClassPoint.LEFT) m = i;
            }
            e = new tEdge(S[0], S[m]);
        }

        void GiftWraps(List<Point3D> S, ref List<Point3D> Hull)
        {
            Hull = new List<Point3D>();
            int n = S.Count;

            int a = 0;
            for (int i = 1; i < n; i++)
                if (Cmp(S[i], S[a]) < 0) a = i;

            for (int m = 0; m < n; m++)
            {
                Point3D tmp = S[a];
                S[a] = S[m];
                S[m] = tmp;

                Hull.Add(S[m]);

                a = m + 1;
                if (a > n + 1) a -= n;
                for (int i = m + 2; i < n + 1; i++)
                {
                    int l = i;
                    if (l >= n) l -= n;
                    ClassPoint c = Classify(S[l], S[m], S[a]);
                    if (c == ClassPoint.BEYOND || c == ClassPoint.LEFT)
                        a = l;
                }
                if (a == 0)
                {
                    return;
                }
            }
        }

        Boolean ClipPolyToEdge(List<Point3D> S, tEdge e, ref List<Point3D> Result)
        {
            List<Point3D> Poly = new List<Point3D>();
            Point3D crossingPt = new Point3D();
            Point3D org = new Point3D();
            Point3D dest = new Point3D();
            tEdge rE = new tEdge();

            Boolean orgIsInside, destIsInside;
            ClassEdge aCross;

            double t = .0;
            for (int i = 0; i < S.Count; i++)
            {
                int l = i + 1;
                if (l > S.Count - 1) l = 0;

                org = S[i];
                dest = S[l];
                rE.org = org;
                rE.dest = dest;

                orgIsInside = (Classify(org, e.org, e.dest) != ClassPoint.LEFT);
                destIsInside = (Classify(dest, e.org, e.dest) != ClassPoint.LEFT);

                if (orgIsInside != destIsInside)
                {
                    aCross = Intersect(e, rE, ref t);
                    crossingPt.x = (e.org.x + t * (e.dest.x - e.org.x));
                    crossingPt.y = (e.org.y + t * (e.dest.y - e.org.y));
                }
                if (orgIsInside && destIsInside)
                    Poly.Add(dest);
                else
                    if (orgIsInside && (!destIsInside))
                        Poly.Add(crossingPt);
                    else
                        if ((!orgIsInside) && (!destIsInside))
                        {
                            //;//Poly.Add(org);
                        }
                        else
                            if (!orgIsInside && destIsInside)
                            {
                                Poly.Add(crossingPt);
                                if (Cmp(dest, crossingPt) != 0)
                                    Poly.Add(dest);
                            }
            }
            if (Poly.Count > 0)
            {
                Result = new List<Point3D>();
                for (int i = 0; i < Poly.Count; i++)
                    Result.Add(Poly[i]);
                return true;
            }
            else
            {
                Result = new List<Point3D>();
                return false;
            }
        }

        void ClipPoligon(List<Point3D> S, List<Point3D> P, ref List<Point3D> Result)
        {
            List<Point3D> q = new List<Point3D>();
            List<Point3D> r = new List<Point3D>();

            tEdge rE = new tEdge();

            bool flag = true;

            for (int i = 0; i < S.Count; i++)
                q.Add(S[i]);
            for (int i = 0; i < P.Count; i++)
            {
                int l = i + 1;
                if (l > P.Count - 1) l = 0;
                rE.org = P[i];
                rE.dest = P[l];
                if (ClipPolyToEdge(q, rE, ref r))
                {
                    q = new List<Point3D>();
                    for (int j = 0; j < r.Count; j++)
                        q.Add(r[j]);
                }
                else
                {
                    flag = false;
                    break;
                }
            }
            if (flag)
            {
                Result = new List<Point3D>();
                for (int i = 0; i < q.Count; i++)
                    Result.Add(q[i]);
            }
        }

        void CalcGRADIENT(List<Point3D> TRG, ref double grad)
        {
            double x1 = TRG[0].x;
            double x2 = TRG[1].x;
            double x3 = TRG[2].x;
            double y1 = TRG[0].y;
            double y2 = TRG[1].y;
            double y3 = TRG[2].y;
            double z1 = TRG[0].z;
            double z2 = TRG[1].z;
            double z3 = TRG[2].z;

            double det = x2 * y3 + x1 * y2 + x3 * y1 - x2 * y1 - x3 * y2 - x1 * y3;
            grad = .0;
            if (Math.Abs(det) > 1e-6)
            {
                double det1 = (z1 * x2 * y3 + z3 * x1 * y2 + z2 * x3 * y1) - (z3 * x2 * y1 + z2 * x1 * y3 + z1 * x3 * y2);
                double det2 = z2 * y3 + z1 * y2 + z3 * y1 - z2 * y1 - z3 * y2 - z1 * y3;
                double det3 = x2 * z3 + x1 * z2 + x3 * z1 - x2 * z1 - x3 * z2 - x1 * z3;

                KoefA = det1 / det;
                KoefB = det2 / det;
                KoefC = det3 / det;
                grad = Math.Atan2(KoefC, KoefB) / Math.PI * 180;
                if (Math.Abs(KoefB) > 1e-6) rk = KoefC / KoefB;
                else rk = 777;
            }
        }
        
        void LokMiMA(double a,double b,double c,ref double lMin,ref double lMax){
            lMin = a;
            if (b < lMin) lMin = b;
            if (c < lMin) lMin = c;
            lMax = a;
            if (b > lMax) lMax = b;
            if (c > lMax) lMax = c;
        }
        void FillHull(ref Graphics canvas, ref List<Point3D> clip, SystemDraw.RectangleF workRect, Color cv)
        {
            PointF[] MasPoint = new PointF[clip.Count + 1];
            for (int i = 0; i < clip.Count; i++)
            {
                MasPoint[i] = D2D.D2DVisualizer.Scalable(new PointF((float)clip[i].x, (float)clip[i].y), new RectangleF(0, 0, 1, 1), workRect);

            }
            MasPoint[clip.Count] = MasPoint[0];
            Brush mybr = new SolidBrush(cv);
            canvas.FillPolygon(mybr, MasPoint);
        }        

        public void DrawGradientPaint(ref Graphics canvas, ref Numeric2DArray Z, SystemDraw.RectangleF workRect)
        {
            Na = Z.NSize;
            Nb = Z.MSize;
            dZ = (Z.Max - Z.Min) / KIZO;
            GmaxZ = Z.Max;
            GminZ = Z.Min;


            int n = (int)Nb;
            int m = (int)Na; 
            
            TRG = new List<Point3D>();
            S1 = new List<Point3D>();
            for (int i = 0; i < m - 1; i++)
            {
                for (int j = 0; j < n - 1; j++)
                {
                    double z1 = Z.Items[i][j];
                    double z2 = Z.Items[i + 1][j];
                    double z3 = Z.Items[i][j + 1];
                    double x1 =  xj(j);
                    double x2 =  xj(j);
                    double x3 =  xj(j + 1);
                    double y1 =  yi(i);
                    double y2 =  yi(i + 1);
                    double y3 =  yi(i);
                    NomMin = 1;
                    NomMax = 1;
                    if (z2 < z1) NomMin = 2;
                    if (z3 < z1 && z3 < z2) NomMin = 3;
                    NomMax = 1;
                    if (z2 > z1) NomMax = 2;
                    if (z3 > z1 && z3 > z2) NomMax = 3;


                    LokMiMA(z1, z2, z3, ref MinZ, ref MaxZ);

                    int icv;
                    Color cv;

                    S1.Clear();
                    TRG.Clear();
                    if (Math.Abs(MinZ - MaxZ) < 1e-7)
                    {
                        S1.Add(new Point3D(x1, y1, z1));
                        S1.Add(new Point3D(x2, y2, z2));
                        S1.Add(new Point3D(x3, y3, z3));
                        GiftWraps(S1, ref Hull1);
                        icv = (int)((MinZ - GminZ - dZ / 2) / dZ) + 1;
                        //if (icv > 52) icv = 52;
                        cv = D3D.Helpers.GenerateColor.GetColor(KIZO, icv);
                        FillHull(ref canvas, ref Hull1, workRect, cv);
                    }
                    else
                    {
                        //Point3D pp = new Point3D();
                        TRG.Add(new Point3D(x1, y1, z1));
                        TRG.Add(new Point3D(x2, y2, z2));
                        TRG.Add(new Point3D(x3, y3, z3));

                        CalcGRADIENT(TRG, ref Grad);

                        S1.Add(new Point3D(x1, y1, z1));
                        S1.Add(new Point3D(x2, y2, z2));
                        S1.Add(new Point3D(x3, y3, z3));

                        GiftWraps(S1, ref Hull1);
                        DrawPro123(ref canvas, TRG, workRect);
                    }
                    z1 = Z.Items[i + 1][j];
                    z2 = Z.Items[i + 1][j + 1];
                    z3 = Z.Items[i][j + 1];
                    x1 =  xj(j);
                    x2 =  xj(j + 1);
                    x3 =  xj(j + 1);
                    y1 =  yi(i + 1);
                    y2 =  yi(i + 1);
                    y3 =  yi(i);
                    NomMin = 1;
                    NomMax = 1;
                    if (z2 < z1) NomMin = 2;
                    if (z3 < z1 && z3 < z2) NomMin = 3;
                    NomMax = 1;
                    if (z2 > z1) NomMax = 2;
                    if (z3 > z1 && z3 > z2) NomMax = 3;

                    LokMiMA(z1, z2, z3, ref MinZ, ref MaxZ);

                    S1.Clear();
                    if (Math.Abs(MinZ - MaxZ) < 1e-7)
                    {
                        //Point3D pp = new Point3D();
                        S1.Add(new Point3D(x1, y1, z1));
                        S1.Add(new Point3D(x2, y2, z2));
                        S1.Add(new Point3D(x3, y3, z3));

                        GiftWraps(S1, ref Hull1);
                        
                        icv = (int)((MinZ - GminZ - dZ / 2) / dZ) + 1;
                        //if (icv > 52) icv = 52;
                        cv = Helpers.GenerateColor.GetColor(KIZO, icv);
                        FillHull(ref canvas, ref Hull1, workRect, cv);
                        continue;
                    }
                    TRG.Clear();
                    S1.Clear();
                    //Point3D pp = new Point3D();
                    TRG.Add(new Point3D(x1, y1, z1));
                    TRG.Add(new Point3D(x2, y2, z2));
                    TRG.Add(new Point3D(x3, y3, z3));
                    CalcGRADIENT(TRG, ref Grad);

                    S1.Add(new Point3D(x1, y1, z1));
                    S1.Add(new Point3D(x2, y2, z2));
                    S1.Add(new Point3D(x3, y3, z3));
                    GiftWraps(S1, ref Hull1);
                    DrawPro123(ref canvas, TRG, workRect);
                }
            }
            //img.Save("file.jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
        }
        
        void DrawPro123(ref Graphics canvas, List<Point3D> TRG, SystemDraw.RectangleF workRect)
        {
            List<Point3D> S2 = new List<Point3D>();
            double x1 = TRG[0].x; double y1 = TRG[0].y;
            double x2 = TRG[1].x; double y2 = TRG[1].y;
            double x3 = TRG[2].x; double y3 = TRG[2].y;
            double xs = (x1 + x2 + x3) / 3.0;
            double ys = (y1 + y2 + y3) / 3.0;
            double Det = KoefC * KoefC + KoefB * KoefB;
            double b1 = xs * KoefC - ys * KoefB;
            
            double b2 = x1 * KoefB + y1 * KoefC;
            double Det1 = b1 * KoefC + b2 * KoefB;
            double Det2 = b2 * KoefC - b1 * KoefB;
            double xp1 = Det1 / Det;
            double yp1 = Det2 / Det;
            double rd1 = Math.Sqrt((xp1 - x1) * (xp1 - x1) + (yp1 - y1) * (yp1 - y1));

            b2 = x2 * KoefB + y2 * KoefC;
            Det1 = b1 * KoefC + b2 * KoefB;
            Det2 = b2 * KoefC - b1 * KoefB;
            double xp2 = Det1 / Det;
            double yp2 = Det2 / Det;
            double rd2 = Math.Sqrt((xp2 - x2) * (xp2 - x2) + (yp2 - y2) * (yp2 - y2));

            b2 = x3 * KoefB + y3 * KoefC;
            Det1 = b1 * KoefC + b2 * KoefB;
            Det2 = b2 * KoefC - b1 * KoefB;
            double xp3 = Det1 / Det;
            double yp3 = Det2 / Det;
            double rd3 = Math.Sqrt((xp3 - x3) * (xp3 - x3) + (yp3 - y3) * (yp3 - y3));

            double Dmax = rd1;
            if (rd2 > Dmax) Dmax = rd2;
            if (rd3 > Dmax) Dmax = rd3;

            double xrmin = .0, xrmax = .0;
            double yrmin = .0, yrmax = .0;
            switch (NomMin)
            {
                case 1:
                    xrmin = xp1;
                    yrmin = yp1;
                    break;
                case 2:
                    xrmin = xp2;
                    yrmin = yp2;
                    break;
                case 3:
                    xrmin = xp3;
                    yrmin = yp3;
                    break;
            }
            switch (NomMax)
            {
                case 1:
                    xrmax = xp1;
                    yrmax = yp1;
                    break;
                case 2:
                    xrmax = xp2;
                    yrmax = yp2;
                    break;
                case 3:
                    xrmax = xp3;
                    yrmax = yp3;
                    break;
            }
            int iTop,iBot;
            iBot = (int)Math.Round((MinZ - GminZ - dZ / 2.0) / dZ) + 1;
            iTop = (int)Math.Round((MaxZ - GminZ + dZ / 2.0) / dZ) + 1;
            if (iBot < 0) iBot = 0;
            if (iTop > KIZO + 1) iTop = KIZO + 1;
            MasP.Clear();

            for (int i = iBot; i <= iTop; i++)
            {
                double IZOk = GminZ + (i - 1) * dZ;
                Point3D pp = new Point3D();
                pp.x = xrmin + (IZOk - MinZ) / (MaxZ - MinZ) * (xrmax - xrmin);
                pp.y = yrmin + (IZOk - MinZ) / (MaxZ - MinZ) * (yrmax - yrmin);
                MasP.Add(pp);
            }
            double fd;
            if (Math.Abs(KoefB) > 1e-6)
            {
                fd = Math.Sqrt((Dmax * Dmax * KoefB * KoefB) / (KoefC * KoefC + KoefB * KoefB));
            }
            else
            {
                fd = Math.Sqrt((Dmax * Dmax * KoefC * KoefC) / (KoefC * KoefC + KoefB * KoefB));
            }
            MasPd1.Clear();
            MasPd2.Clear();
            for (int i = iBot; i <= iTop; i++)
            {
                Point3D pp = new Point3D();
                if (Math.Abs(KoefB) > 1e-6)
                {
                    pp.y = MasP[i - iBot].y + fd;
                    pp.x = MasP[i - iBot].x + KoefC / KoefB * (MasP[i - iBot].y - pp.y);
                }
                else
                {
                    pp.x = MasP[i - iBot].x + fd;
                    pp.y = MasP[i - iBot].y;
                }
                MasPd1.Add(pp);

                if (Math.Abs(KoefB) > 1e-6)
                {
                    pp.y = MasP[i - iBot].y - fd;
                    pp.x = MasP[i - iBot].x + KoefC / KoefB * (MasP[i - iBot].y - pp.y);
                }
                else
                {
                    pp.x = MasP[i - iBot].x - fd;
                    pp.y = MasP[i - iBot].y;
                }
                MasPd2.Add(pp);
            }
            for (int i = iBot; i < iTop; i++)
            {
                S2.Add(MasPd1[i - iBot]);
                S2.Add(MasPd1[i + 1 - iBot]);
                S2.Add(MasPd2[i - iBot]);
                S2.Add(MasPd2[i + 1 - iBot]);
                GiftWraps(S2, ref Hull2);
                ClipPoligon(Hull2, Hull1, ref Clip);
                if (Clip.Count > 2)
                    FillHull(ref canvas, ref Clip, workRect, Helpers.GenerateColor.GetColor(KIZO, i));
            }

        }
    }
}
